package com.lp.security.component;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.lp.biz.system.mapper.*;
import com.lp.biz.system.model.entity.SysEndpoints;
import com.lp.biz.system.model.entity.SysMenu;
import com.lp.biz.system.model.entity.SysMenuEndpointsRel;
import com.lp.biz.system.model.entity.SysRole;
import com.lp.biz.system.model.entity.SysRoleEndpointsRel;
import com.lp.common.annotation.PermissionAnnotation;
import com.lp.security.schedule.RegressionId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

import static com.lp.common.constant.CommonConstant.NEGATE;
import static com.lp.common.constant.CommonConstant.SURE;

/**
 * @program: backend
 * @description:
 * @author: Ke.Song
 * @since: 2024-05-19 0:03:47
 */
@Component
public class UrlListPrinter implements CommandLineRunner {

    @Autowired
    private SysRoleMapper roleMapper;

    @Autowired
    private RegressionId regressionId;

    @Autowired
    private SysMenuMapper menuMapper;

    @Autowired
    private SysEndpointsMapper endpointsMapper;

    @Autowired
    private SysRoleEndpointsRelMapper roleEndpointsRelMapper;

    @Autowired
    private SysMenuEndpointsRelMapper menuEndpointsRelMapper;

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    private final Map<String, SysRole> ROLE_MAPS = new HashMap<>(6);

    private final Map<String, SysMenu> MENU_MAPS = new HashMap<>();

    private final Map<String, SysEndpoints> ENDPOINTS_MAPS = new HashMap<>();

    private final Set<Integer> PUBLIC_SET = new HashSet<>();

    private final List<Class<? extends Annotation>> methodClass = List.of(
            GetMapping.class,
            PostMapping.class,
            DeleteMapping.class,
            PutMapping.class);


    @Override
    public void run(String... args) {
        Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
        List<SysMenuEndpointsRel> menuEndpointsRelList = new ArrayList<>();
        List<SysRoleEndpointsRel> roleEndpointsRelList = new ArrayList<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
            RequestMappingInfo info = entry.getKey();
            HandlerMethod handlerMethod = entry.getValue();
            Method method = handlerMethod.getMethod();
            for (Class<? extends Annotation> c : methodClass) {
                if (method.getAnnotation(c) != null) {
                    checkMapping(method, info, menuEndpointsRelList, roleEndpointsRelList);
                    break;
                }
            }
        }
        menuEndpointsRelMapper.delete(new QueryWrapper<>());
        roleEndpointsRelMapper.delete(new QueryWrapper<>());
        if (!menuEndpointsRelList.isEmpty()) {
            menuEndpointsRelMapper.insertBatch(menuEndpointsRelList);
        }
        if (!ENDPOINTS_MAPS.isEmpty()) {
            Set<Integer> collect = ENDPOINTS_MAPS.values()
                    .stream()
                    .map(SysEndpoints::getId)
                    .collect(Collectors.toSet());
            endpointsMapper.update(null, new UpdateWrapper<SysEndpoints>().lambda()
                    .in(SysEndpoints::getId, collect)
                    .eq(SysEndpoints::getIsEnabled, SURE)
                    .set(SysEndpoints::getIsEnabled, NEGATE));
            menuEndpointsRelMapper.delete(new QueryWrapper<SysMenuEndpointsRel>().lambda()
                    .in(SysMenuEndpointsRel::getSysEndpointsId, collect));
            endpointsMapper.delete(new QueryWrapper<SysEndpoints>().lambda().in(SysEndpoints::getId, collect));
            regressionId.deletionId("sys_endpoints");
            ENDPOINTS_MAPS.clear();
        }
        if (!PUBLIC_SET.isEmpty()) {
            endpointsMapper.update(null, new UpdateWrapper<SysEndpoints>().lambda()
                    .notIn(SysEndpoints::getId, PUBLIC_SET)
                    .set(SysEndpoints::getIsPublic, NEGATE));
            endpointsMapper.update(null, new UpdateWrapper<SysEndpoints>().lambda()
                    .in(SysEndpoints::getId, PUBLIC_SET)
                    .set(SysEndpoints::getIsPublic, SURE));
            PUBLIC_SET.clear();
        }
        if (!roleEndpointsRelList.isEmpty()) {
            roleEndpointsRelMapper.insertBatch(roleEndpointsRelList);
        }
    }

    @Autowired
    private void getMenuMaps() {
        MENU_MAPS.clear();
        for (SysMenu menu : menuMapper.selectList(new QueryWrapper<>())) {
            MENU_MAPS.put(menu.getCode(), menu);
        }
    }

    @Autowired
    private void getEndpointsMaps() {
        ENDPOINTS_MAPS.clear();
        for (SysEndpoints endpoints : endpointsMapper.selectList(new QueryWrapper<>())) {
            ENDPOINTS_MAPS.put(endpoints.getMethod() + ":" + endpoints.getUrl(), endpoints);
        }
    }

    @Autowired
    private void getRoleMaps() {
        ROLE_MAPS.clear();
        for (SysRole role : roleMapper.selectList(new QueryWrapper<>())) {
            ROLE_MAPS.put(role.getCode(), role);
        }
    }

    private void checkMapping(Method method, RequestMappingInfo info, List<SysMenuEndpointsRel> menuEndpointsRelList, 
                              List<SysRoleEndpointsRel> roleEndpointsRelList) {
        Set<SysEndpoints> temps = new HashSet<>(6);
        Set<RequestMethod> methods = info.getMethodsCondition().getMethods();
        Set<PathPattern> patterns = new HashSet<>(6);
        PathPatternsRequestCondition pathPatternsCondition = info.getPathPatternsCondition();
        if (pathPatternsCondition != null) {
            patterns.addAll(pathPatternsCondition.getPatterns());
        }
        for (RequestMethod conditionMethod : methods) {
            String name = conditionMethod.name();
            String path;
            for (PathPattern pattern : patterns) {
                path = pattern.getPatternString().replaceAll("\\{[a-zA-Z]*}", "\\\\d*");
                String key = name + ":" + path;
                SysEndpoints endpoints = ENDPOINTS_MAPS.remove(key);
                if (endpoints == null) {
                    endpoints = new SysEndpoints();
                    endpoints.setMethod(name);
                    endpoints.setUrl(path);
                    endpoints.setIsEnabled(SURE);
                    endpointsMapper.insert(endpoints);
                }
                temps.add(endpoints);
            }
        }
        int isPublic = SURE;
        PermissionAnnotation annotation = method.getAnnotation(PermissionAnnotation.class);
        if (annotation != null) {
            isPublic = NEGATE;
            for (String code : annotation.menus()) {
                SysMenu menu = MENU_MAPS.get(code);
                if (menu != null) {
                    for (SysEndpoints temp : temps) {
                        SysMenuEndpointsRel rel = new SysMenuEndpointsRel();
                        rel.setSysMenuId(menu.getId());
                        rel.setSysEndpointsId(temp.getId());
                        menuEndpointsRelList.add(rel);
                    }
                }
            }
            for (String code : annotation.roles()) {
                SysRole role = ROLE_MAPS.get(code);
                if (role != null) {
                    for (SysEndpoints temp : temps) {
                        SysRoleEndpointsRel rel = new SysRoleEndpointsRel();
                        rel.setSysRoleId(role.getId());
                        rel.setSysEndpointsId(temp.getId());
                        roleEndpointsRelList.add(rel);
                    }
                }
            }
        }
        if (isPublic == SURE) {
            PUBLIC_SET.addAll(temps.stream().map(SysEndpoints::getId).collect(Collectors.toSet()));
        }
    }
}
